/*
 * $Id$
 *
 * SARL is an general-purpose agent programming language.
 * More details on http://www.sarl.io
 *
 * Copyright (C) 2014-2024 SARL.io, the Original Authors and Main Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sarl.lang.mwe2.codebuilder.extractor;

import com.google.inject.ImplementedBy;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.xbase.lib.Functions.Function4;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xtext.generator.model.TypeReference;

import io.sarl.lang.mwe2.codebuilder.config.CodeBuilderConfig;
import io.sarl.lang.mwe2.keywords.GrammarKeywordAccessFragment2;

/** Extract elements from the grammar.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 */
@ImplementedBy(NoBacktrackGrammarCodeElementExtractor.class)
public interface CodeElementExtractor {

	/** Replies the fully qualified name for the classifier.
	 *
	 * <p>Copied from AbstractSubCodeBuilderFragment.
	 *
	 * @param classifier the classifier.
	 * @return the fully qualified name for the given classifier.
	 */
	TypeReference newTypeReference(EClassifier classifier);

	/** Replies the fully qualified name for the type.
	 *
	 * @param type the type.
	 * @return the fully qualified name for the given classifier.
	 * @since 0.15
	 */
	TypeReference newTypeReference(Class<?> type);

	/** Replies the type that is generated by a rule.
	 *
	 * @param rule the rule.
	 * @return the generated type.
	 */
	EClassifier getGeneratedTypeFor(AbstractRule rule);

	/** Replies the first classifier that is defined in the given grammar element.
	 *
	 * @param grammarElement the grammar element.
	 * @return the classifier or {@code null} if none.
	 */
	EClassifier getGeneratedTypeFor(EObject grammarElement);

	/** Initialize the extractor.
	 *
	 * @param grammar the grammar to use.
	 */
	void initialize(Grammar grammar);

	/** Replies the interface for the code builder that is creating the element of the given name.
	 *
	 * @param elementName the name of the element.
	 * @return the interface.
	 */
	@Pure
	TypeReference getElementBuilderInterface(String elementName);

	/** Replies the implementation for the code builder that is creating the element of the given name.
	 *
	 * @param elementName the name of the element.
	 * @return the interface.
	 */
	@Pure
	TypeReference getElementBuilderImpl(String elementName);

	/** Replies the custom implementation for the code builder that is creating the element of the given name.
	 *
	 * @param elementName the name of the element.
	 * @return the interface.
	 */
	@Pure
	TypeReference getElementBuilderImplCustom(String elementName);

	/** Replies the base package for the code builder.
	 *
	 * @return the base package for the code builder.
	 */
	@Pure
	String getBasePackage();

	/** Replies the base package for the builders.
	 *
	 * @return the base package for the builders.
	 */
	@Pure
	String getAppenderPackage();

	/** Replies the base package for the documentation tools.
	 *
	 * @return the base package for the documentation tools.
	 */
	@Pure
	String getDocumentationPackage();

	/** Replies the adapter for inner-block documentation.
	 *
	 * @return the adapter.
	 */
	@Pure
	TypeReference getInnerBlockDocumentationAdapter();

	/** Replies the base package for the scope provider for serializer.
	 *
	 * @return the base package for the scope provider.
	 * @since 0.15
	 */
	@Pure
	default String getSerializerScopeProviderPackage() {
		return getSerializerPackage();
	}

	/** Replies the scope provider for serializer.
	 *
	 * @return the provider.
	 * @since 0.15
	 */
	@Pure
	TypeReference getSerializerScopeProvider();

	/** Replies the type parameters' scope for serializer.
	 *
	 * @return the scope.
	 * @since 0.15
	 */
	@Pure
	TypeReference getSerializerTypeParameterScope();

	/** Replies the base package for the serialization tools.
	 *
	 * @return the base package for the serialization tools.
	 */
	@Pure
	String getSerializerPackage();

	/** Replies the base package for the builders.
	 *
	 * @return the base package for the builders.
	 */
	@Pure
	String getBuilderPackage();

	/** Replies the type associated to the top elements.
	 *
	 * @return the type of the top elements.
	 */
	@Pure
	TypeReference getLanguageTopElementType();

	/** Replies the type associated to the utility for accessing the keywords.
	 *
	 * @return the type of the languaage keyword accessor.
	 * @see GrammarKeywordAccessFragment2
	 */
	@Pure
	TypeReference getLanguageKeywordAccessor();

	/** Replies the implementation for the code appender that is creating the element of the given name.
	 *
	 * @param elementName the name of the element.
	 * @return the interface.
	 */
	@Pure
	TypeReference getElementAppenderImpl(String elementName);

	/** Replies the custom implementation for the code appender that is creating the element of the given name.
	 *
	 * @param elementName the name of the element.
	 * @return the interface.
	 */
	@Pure
	TypeReference getElementAppenderImplCustom(String elementName);

	/** Replies the implementation for the code adapter.
	 *
	 * @return the implementation.
	 */
	@Pure
	TypeReference getAbstractAppenderImpl();

	/** Replies the base package for the ecore elements of the grammar.
	 *
	 * @return the base package for the ecore elements.
	 */
	@Pure
	String getLanguageBasePackage();

	/** Replies the type for the scripts from the grammar.
	 *
	 * @return the language script.
	 */
	@Pure
	TypeReference getLanguageScriptInterface();

	/** Replies the the top elements of the grammar.
	 *
	 * @param grammar the grammar.
	 * @param config the configuration.
	 * @return the top elements.
	 */
	@Pure
	Iterable<ElementDescription> getTopElements(Grammar grammar, CodeBuilderConfig config);

	/** Extract type members from the given rule.
	 *
	 * <p>The callback functions are invoked for each member discovered in the grammar.
	 *
	 * <p>When a callback is replying a value, the visiting is stopping and the value is replied.
	 *
	 * @param <T> type of the value to extract.
	 * @param element the element that is containing the members.
	 * @param grammarContainer the member's container in the grammar. It is should be called by the grammar element
	 *     of the given element.
	 * @param constructorCallback the function to call on each discovered constructor.
	 *     The parameters of the callback function are: the grammar container given to the visiting function;
	 *     and the grammar container of the member.
	 * @param namedMemberCallback the function to call on each discovered named member. A named member is
	 *     a member that has a field "name" defined in the grammar.
	 *     The parameters of the callback function are: the grammar container given to the visiting function;
	 *     the grammar container of the member, and the name of the member.
	 * @param typeReferencingMemberCallback the function to call on each discovered member that are referencing
	 *     types, for example a kind of "import".
	 *     The parameters of the callback function are: the grammar container given to the visiting function;
	 *     the grammar container of the member, and the name of the member.
	 * @return the first value replied by the callback, or {@code null}.
	 */
	<T> T visitMemberElements(
			ElementDescription element,
			EObject grammarContainer,
			Function4<? super CodeElementExtractor, ? super EObject, ? super EObject, ? super EClassifier, ? extends T> constructorCallback,
			Function4<? super CodeElementExtractor, ? super EObject, ? super EObject, ? super EClassifier, ? extends T> namedMemberCallback,
			Function4<? super CodeElementExtractor, ? super EObject, ? super EObject,
					? super EClassifier, ? extends T> typeReferencingMemberCallback);

	/** Construct an element description.
	 *
	 * @param name the name of the element.
	 * @param grammarComponent the grammar component of the element.
	 * @param elementType the type of the element.
	 * @param commonType the common type to all the elements in the same category.
	 * @return the element description.
	 */
	default ElementDescription newElementDescription(String name, EObject grammarComponent, EClassifier elementType,
			EClassifier commonType) {
		return newElementDescription(name, grammarComponent, elementType, newTypeReference(commonType));
	}

	/** Construct an element description.
	 *
	 * @param name the name of the element.
	 * @param grammarComponent the grammar component of the element.
	 * @param elementType the type of the element.
	 * @param commonType the common type to all the elements in the same category.
	 * @return the element description.
	 */
	default ElementDescription newElementDescription(String name, EObject grammarComponent, EClassifier elementType,
			Class<?> commonType) {
		return newElementDescription(name, grammarComponent, elementType, new TypeReference(commonType));
	}

	/** Construct an element description.
	 *
	 * @param name the name of the element.
	 * @param grammarComponent the grammar component of the element.
	 * @param elementType the type of the element.
	 * @param commonType the common type to all the elements in the same category.
	 * @return the element description.
	 */
	ElementDescription newElementDescription(String name, EObject grammarComponent, EClassifier elementType,
			TypeReference commonType);

	/** Replies the description of the formal parameters.
	 *
	 * @return the description of the formal parameter.
	 */
	ElementDescription getFormalParameter();

	/** Replies the type of the formal parameter container.
	 *
	 * @return the type of the formal parameter container.
	 */
	TypeReference getFormalParameterContainerType();

	/** Replies the description of the type parameters.
	 *
	 * @return the description of the type parameter.
	 */
	ElementDescription getTypeParameter();

	/** Description of an element.
	 *
	 * @param name the name of the element.
	 * @param grammarComponent the grammar component of the element.
	 * @param elementType the type for the element.
	 * @param commonSuperType the common type to all the elements in the same category.
	 * @param builderInterfaceType the type for the interface that corresponds to this element builder.
	 * @param builderImplementationType the type for the class that corresponds to this element builder.
	 * @param builderCustomImplementationType the type for the class that corresponds to this element builder.
	 * @param appenderType the type for the class that corresponds to this element appender.
	 * @param annotationInfo indicates if the annotationInfo field is declared for the element.
	 * @author $Author: sgalland$
	 * @version $FullVersion$
	 * @mavengroupid $GroupId$
	 * @mavenartifactid $ArtifactId$
	 */
	record ElementDescription(
			String name, EObject grammarComponent,
			TypeReference elementType, TypeReference commonSuperType, TypeReference builderInterfaceType,
			TypeReference builderImplementationType, TypeReference builderCustomImplementationType,
			TypeReference appenderType, boolean annotationInfo) {

		@Override
		public boolean equals(Object obj) {
			if (obj == null) {
				return false;
			}
			if (this == obj) {
				return true;
			}
			if (ElementDescription.class.equals(obj.getClass())) {
				final var desc = (ElementDescription) obj;
				return elementType().equals(desc.elementType())
					&& name().equals(desc.name())
					&& builderInterfaceType().equals(desc.builderInterfaceType())
					&& builderImplementationType().equals(desc.builderImplementationType());
			}
			return false;
		}

		@Override
		public String toString() {
			return elementType().toString();
		}

	}

}
